package com.vmj.instructions.references;

import com.vmj.heap.*;
import com.vmj.heap.VMClass;
import com.vmj.heap.VMObject;
import com.vmj.instructions.base.Index16Instruction;
import com.vmj.instructions.base.MethodInvokeLogic;
import com.vmj.rtda.Frame;
import com.vmj.rtda.OperandStack;

/**
 * 用于实例方法调用
 *
 * Created by yangshoujun on 2017/8/27 下午3:25.
 */
public class InvokeVirtual extends Index16Instruction {

    @Override
    public void execute(Frame frame) {
        VMClass currentCls = frame.getMethod().getClazz();
        RuntimeConstantPool cp = currentCls.getRuntimeConstantPool();
        MethodRef methodRef = (MethodRef) cp.getConstant(this.getIndex()).getValue();
        Method resolvedMethod = methodRef.resolvedMethod();

        if(resolvedMethod.isStatic()) {
            throw new RuntimeException("java.lang.IncompatibalClassChangeError");
        }

        VMObject thisRef = frame.getOperandStack().getRefFromTop(resolvedMethod.getArgSlotCount() - 1);
        if("println".equals(methodRef.getName()) || "print".equals(methodRef.getName())) {
            doNothing();
        }

        if(thisRef == null) {
            if(methodRef.getName().equals("println")) {
                println(frame, methodRef.getDescriptor());
            }
            if(methodRef.getName().equals("print")) {
                print(frame.getOperandStack(), methodRef.getDescriptor());
            }
            return;
        }

        if(resolvedMethod.isProtected()
                && resolvedMethod.getClazz().isSubClassOf(currentCls)
                && !resolvedMethod.getClazz().getPackageName().equals(currentCls.getPackageName())
                && !thisRef.getClazz().equals(currentCls)
                && !thisRef.getClazz().isSubClassOf(currentCls)) {

            if(!(thisRef.getClazz().isArray() && resolvedMethod.getName().equals("clone"))) {
                throw new RuntimeException("java.lang.IllegalAccessError");
            }
        }

        Method methodToBeInvoked = MethodRef.lookupMethodInClass(thisRef.getClazz(), methodRef.getName(), methodRef.getDescriptor());
        if(methodToBeInvoked == null || methodToBeInvoked.isAbstract()) {
            throw new RuntimeException("java.lang.AbstractMethodError");
        }
        MethodInvokeLogic.invokeMethod(frame, methodToBeInvoked);
    }

    private void println(Frame frame, String descriptor) {
        OperandStack stack = frame.getOperandStack();
        if("(Z)V".equals(descriptor)) {
            System.out.println(stack.popInt());
        } else if("(C)V".equals(descriptor)) {
            System.out.println((char)stack.popInt());
        } else if("(B)V".equals(descriptor)) {
            System.out.println(stack.popInt());
        } else if("(S)V".equals(descriptor)) {
            System.out.println(stack.popInt());
        } else if("(I)V".equals(descriptor)) {
            System.out.println(stack.popInt());
        } else if("(J)V".equals(descriptor)) {
            System.out.println(stack.popLong());
        } else if("(F)V".equals(descriptor)) {
            System.out.println(stack.popFloat());
        } else if("(D)V".equals(descriptor)) {
            System.out.println(stack.popDouble());
        } else if("()V".equals(descriptor)) {
            System.out.println();
        } else if("(Ljava/lang/String;)V".equals(descriptor)) {
            VMObject obj = stack.popRef();
            VMObject strObj = obj.getRefVar("value", "[C");
            char[] chars = (char[]) strObj.getData();
            String str = new String(chars);
            System.out.println(str);
        } else if("(Ljava/lang/Object;)V".equals(descriptor)) {
            VMObject obj = stack.popRef();
            if(obj == null) {
                System.out.println(" null object ");
                return;
            }

            if(obj.getClazz().getName().equals("java/lang/RuntimeException")) {
                VMObject detailMessageObj = obj.getRefVar("detailMessage", "Ljava/lang/String;");
                String detailMessage = null;
                if(detailMessageObj != null) {
                    detailMessage = detailMessageObj.getRefString();
                }

                System.out.println(obj.getClazz().getJavaName() + ": " + detailMessage);
                return;
            }

            if(obj.getClazz().getName().equals("java/lang/String")) {
                System.out.println(obj.getRefString());
                return;
            }

            System.out.println(obj.toString());
        }
        stack.popRef();
    }

    private void print(OperandStack stack, String descriptor) {
        if("(Z)V".equals(descriptor)) {
            System.out.print(stack.popInt());
        } else if("(C)V".equals(descriptor)) {
            System.out.print((char)stack.popInt());
        } else if("(B)V".equals(descriptor)) {
            System.out.print(stack.popInt());
        } else if("(S)V".equals(descriptor)) {
            System.out.print(stack.popInt());
        } else if("(I)V".equals(descriptor)) {
            System.out.print(stack.popInt());
        } else if("(J)V".equals(descriptor)) {
            System.out.print(stack.popLong());
        } else if("(F)V".equals(descriptor)) {
            System.out.print(stack.popFloat());
        } else if("(D)V".equals(descriptor)) {
            System.out.print(stack.popDouble());
        } else if("()V".equals(descriptor)) {
            System.out.print("");
        } else if("(Ljava/lang/String;)V".equals(descriptor)) {
            VMObject obj = stack.popRef();
            VMObject strObj = obj.getRefVar("value", "[C");
            char[] chars = (char[]) strObj.getData();
            String str = new String(chars);
            System.out.print(str);
        }
        stack.popRef();
    }

    private void doNothing() {}

}
